;
(function ($) {
    var regexHex = /^0x[a-f0-9]+$/i,
        regexInfinity = /^[+\-]?infinity$/i,
        regexParseFloat = /^[+\-]?\d*\.?\d*(e[+\-]?\d+)?$/,
        regexTrim = /^\s+|\s+$/g;

    var patterns = {
        numeric: {
            negative: ["(n)", "-n", "- n", "n-", "n -"]
        },
        currency: {
            positive: ["$n", "n$", "$ n", "n $"],
            negative: ["($n)", "-$n", "$-n", "$n-", "(n$)", "-n$", "n-$", "n$-", "-n $", "-$ n", "n $-", "$ n-", "$ -n", "n- $", "($ n)", "(n $)"]
        },
        percent: {
            positive: ["n %", "n%", "%n", "% n"],
            negative: ["-n %", "-n%", "-%n", "%-n", "%n-", "n-%", "n%-", "-% n", "n %-", "% n-", "% -n", "n- %"]
        }
    };

    var defaultCulture = {
        name: "en-US",
        englishName: "English (United States)",
        nativeName: "English",
        isRTL: false,
        language: "en",
        numberFormat: {
            // number groups separator
            ",": ",",
            // decimal separator
            ".": ".",
            // [negativePattern]
            // Note, numberFormat.pattern has no "positivePattern" unlike percent and currency,
            // but is still defined as an array for consistency with them.
            pattern: [1],
            // NumberDecimalDigits
            decimals: 2,
            // NumberGroupSizes
            groupSizes: [3],
            // symbol used for positive numbers (PositiveSign)
            "+": "+",
            // symbol used for negative numbers (NegativeSign)
            "-": "-",
            // symbol used for NaN (Not-A-Number) (NaNSymbol)
            "NaN": "NaN",
            // symbol used for Negative Infinity
            negativeInfinity: "-Infinity",
            // symbol used for Positive Infinity
            positiveInfinity: "Infinity",
            percent: {
                pattern: [0, 0],
                decimals: 2,
                groupSizes: [3],
                ",": ",",
                ".": ".",
                symbol: "%"
            },
            currency: {
                pattern: [0, 0],
                decimals: 2,
                groupSizes: [3],
                ",": ",",
                ".": ".",
                symbol: "$"
            }
        },
        dateTimeFormat: {
            calendarName: "Gregorian_USEnglish",
            "/": "/", // separator of parts of a date (e.g. "/" in 11/05/1955)
            ":": ":", // separator of parts of a time (e.g. ":" in 05:44 PM)
            firstDay: 0, // the first day of the week (0 = Sunday, 1 = Monday, etc)
            days: {
                names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
                namesAbbr: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
                namesShort: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
            },
            months: {
                // full month names (13 months for lunar calendards -- 13th month should be "" if not lunar)
                names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ""],
                namesAbbr: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ""]
            },
            // AM and PM designators in one of these forms:
            // The usual view, and the upper and lower case versions
            //   [ standard, lowercase, uppercase ]
            // The culture does not use AM or PM (likely all standard date formats use 24 hour time)
            //   null
            AM: ["AM", "am", "AM"], // null if empty
            PM: ["PM", "pm", "PM"], // null if empty
            twoDigitYearMax: 2029,
            // set of predefined date and time patterns used by the culture
            // these represent the format someone in this culture would expect
            // to see given the portions of the date that are shown.
            patterns: {
                // short date pattern
                d: "M/d/yyyy",
                // long date pattern
                D: "dddd, MMMM dd, yyyy",
                // short time pattern
                t: "h:mm tt",
                // long time pattern
                T: "h:mm:ss tt",
                // general date time pattern (short time)
                g: "M/d/yyyy h:mm tt",
                // general date time pattern (long time)
                G: "M/d/yyyy h:mm:ss tt",
                // long date, short time pattern
                f: "dddd, MMMM dd, yyyy h:mm tt",
                // long date, long time pattern
                F: "dddd, MMMM dd, yyyy h:mm:ss tt",
                // month/day pattern
                M: "MMMM dd",
                // month/year pattern
                Y: "yyyy MMMM",
                // Universal sortable pattern
                u: "yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss"
            }
        }
    };

    var g = {
        patterns: patterns,
        culture: defaultCulture
    };

    function truncate(value) {
        if (isNaN(value)) {
            return NaN;
        }
        return Math[value < 0 ? "ceil" : "floor"](value);
    }

    function zeroPad(str, count, left) {
        var l;
        for (l = str.length; l < count; l += 1) {
            str = (left ? ("0" + str) : (str + "0"));
        }
        return str;
    };

    function parseNegativePattern(value, nf, negativePattern) {
        var neg = nf["-"],
            pos = nf["+"],
            ret;
        switch (negativePattern) {
            case "n -":
                neg = " " + neg;
                pos = " " + pos;
            /* falls through */
            case "n-":
                if (value.endsWith(neg)) {
                    ret = ["-", value.substr(0, value.length - neg.length)];
                }
                else if (value.endsWith(pos)) {
                    ret = ["+", value.substr(0, value.length - pos.length)];
                }
                break;
            case "- n":
                neg += " ";
                pos += " ";
            /* falls through */
            case "-n":
                if (value.startsWith(neg)) {
                    ret = ["-", value.substr(neg.length)];
                }
                else if (value.startsWith(pos)) {
                    ret = ["+", value.substr(pos.length)];
                }
                break;
            case "(n)":
                if (value.startsWith("(") && value.endsWith(")")) {
                    ret = ["-", value.substr(1, value.length - 2)];
                }
                break;
        }
        return ret || ["", value];
    };

    //// Not implemented. Should use moment.js for
    //// datetime processing.
    //g.parseDate = function (value) {
    //	return value;
    //}

    g.parseInt = function (value) {
        return truncate(g.parseFloat(value));
    }

    g.parseFloat = function (value) {
        var radix = 10;

        var culture = g.culture;
        var ret = NaN,
            nf = culture.numberFormat;

        if (value.indexOf(culture.numberFormat.currency.symbol) > -1) {
            // remove currency symbol
            value = value.replace(culture.numberFormat.currency.symbol, "");
            // replace decimal separator
            value = value.replace(culture.numberFormat.currency["."], culture.numberFormat["."]);
        }

        //Remove percentage character from number string before parsing
        if (value.indexOf(culture.numberFormat.percent.symbol) > -1) {
            value = value.replace(culture.numberFormat.percent.symbol, "");
        }

        // remove spaces: leading, trailing and between - and number. Used for negative currency pt-BR
        value = value.replace(/ /g, "");

        // allow infinity or hexidecimal
        if (regexInfinity.test(value)) {
            ret = parseFloat(value);
        }
        else if (!radix && regexHex.test(value)) {
            ret = parseInt(value, 16);
        }
        else {

            // determine sign and number
            var signInfo = parseNegativePattern(value, nf, patterns.numeric.negative[nf.pattern[0]]),
                sign = signInfo[0],
                num = signInfo[1];

            // #44 - try parsing as "(n)"
            if (sign === "" && nf.pattern[0] !== "(n)") {
                signInfo = parseNegativePattern(value, nf, "(n)");
                sign = signInfo[0];
                num = signInfo[1];
            }

            // try parsing as "-n"
            if (sign === "" && nf.pattern[0] !== "-n") {
                signInfo = parseNegativePattern(value, nf, "-n");
                sign = signInfo[0];
                num = signInfo[1];
            }

            sign = sign || "+";

            // determine exponent and number
            var exponent,
                intAndFraction,
                exponentPos = num.indexOf("e");
            if (exponentPos < 0) exponentPos = num.indexOf("E");
            if (exponentPos < 0) {
                intAndFraction = num;
                exponent = null;
            }
            else {
                intAndFraction = num.substr(0, exponentPos);
                exponent = num.substr(exponentPos + 1);
            }
            // determine decimal position
            var integer,
                fraction,
                decSep = nf["."],
                decimalPos = intAndFraction.indexOf(decSep);
            if (decimalPos < 0) {
                integer = intAndFraction;
                fraction = null;
            }
            else {
                integer = intAndFraction.substr(0, decimalPos);
                fraction = intAndFraction.substr(decimalPos + decSep.length);
            }
            // handle groups (e.g. 1,000,000)
            var groupSep = nf[","];
            integer = integer.split(groupSep).join("");
            var altGroupSep = groupSep.replace(/\u00A0/g, " ");
            if (groupSep !== altGroupSep) {
                integer = integer.split(altGroupSep).join("");
            }
            // build a natively parsable number string
            var p = sign + integer;
            if (fraction !== null) {
                p += "." + fraction;
            }
            if (exponent !== null) {
                // exponent itself may have a number patternd
                var expSignInfo = parseNegativePattern(exponent, nf, "-n");
                p += "e" + (expSignInfo[0] || "+") + expSignInfo[1];
            }
            if (regexParseFloat.test(p)) {
                ret = parseFloat(p);
            }
        }

        return ret;
    }

    g.convertDatePatternToMomentFormat = function (pattern) {
        // Converts .NET date format string to moment.js format
        var result = '',
            token = '';

        function convertToken(t) {
            switch (t) {
                case 'd': return 'D';
                case 'dd': return 'DD';
                case 'ddd': return 'dd';
                case 'yy': return 'YY';
                case 'yyy': case 'yyyy': case 'yyyyy': return 'YYYY';
                case 'zz': return 'ZZ';
                case 'zzz': return 'Z';
                case 'tt': return 'A';
                case 'ff': case 'FF': return 'SS';
                case 'fff': case 'FFF': return 'SSS';
                case 'ffff': case 'FFFF': return 'SSSS';
                case 'fffff': case 'FFFFF': return 'SSSSS';
                case 'ffffff': case 'FFFFFF': return 'SSSSSS';
                case 'fffffff': case 'FFFFFFF': return 'SSSSSSS';
                default:
                    return t;
            }
        }

        for (var i = 0; i < pattern.length; i++) {
            switch (pattern[i]) {
                case 'd': case 'y': case 'z': case 't': case 'f':
                    token += pattern[i];
                    continue;
                default:
                    if (token.length > 0) {
                        result += convertToken(token);
                        token = '';
                    }

                    result += pattern[i];
            }
        }

        if (token.length > 0) {
            result += convertToken(token);
        }

        return result;
    }

    g.formatDate = function (value, format) {
        format = format || g.culture.dateTimeFormat.patterns.G;

        if (format.length === 1) {
            // Maybe pattern shortcut, like "G", "F", "d" etc.
            let patterns = g.culture.dateTimeFormat.patterns;
            format = patterns[format] || format;
        }

        let momentFormat = g.convertDatePatternToMomentFormat(format);

        return moment(value).format(momentFormat);
    }

    // formatNumber
    var formatNumber;
    (function () {
        var expandNumber;

        expandNumber = function (number, precision, formatInfo) {
            var groupSizes = formatInfo.groupSizes,
                curSize = groupSizes[0],
                curGroupIndex = 1,
                factor = Math.pow(10, precision),
                rounded = Math.round(number * factor) / factor;

            if (!isFinite(rounded)) {
                rounded = number;
            }
            number = rounded;

            var numberString = number + "",
                right = "",
                split = numberString.split(/e/i),
                exponent = split.length > 1 ? parseInt(split[1], 10) : 0;
            numberString = split[0];
            split = numberString.split(".");
            numberString = split[0];
            right = split.length > 1 ? split[1] : "";

            if (exponent > 0) {
                right = zeroPad(right, exponent, false);
                numberString += right.slice(0, exponent);
                right = right.substr(exponent);
            }
            else if (exponent < 0) {
                exponent = -exponent;
                numberString = zeroPad(numberString, exponent + 1, true);
                right = numberString.slice(-exponent, numberString.length) + right;
                numberString = numberString.slice(0, -exponent);
            }

            if (precision > 0) {
                right = formatInfo["."] +
                    ((right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision));
            }
            else {
                right = "";
            }

            var stringIndex = numberString.length - 1,
                sep = formatInfo[","],
                ret = "";

            while (stringIndex >= 0) {
                if (curSize === 0 || curSize > stringIndex) {
                    return numberString.slice(0, stringIndex + 1) + (ret.length ? (sep + ret + right) : right);
                }
                ret = numberString.slice(stringIndex - curSize + 1, stringIndex + 1) + (ret.length ? (sep + ret) : "");

                stringIndex -= curSize;

                if (curGroupIndex < groupSizes.length) {
                    curSize = groupSizes[curGroupIndex];
                    curGroupIndex++;
                }
            }

            return numberString.slice(0, stringIndex + 1) + sep + ret + right;
        };

        formatNumber = function (value, format) {
            var culture = g.culture;

            if (!isFinite(value)) {
                if (value === Infinity) {
                    return culture.numberFormat.positiveInfinity;
                }
                if (value === -Infinity) {
                    return culture.numberFormat.negativeInfinity;
                }
                return culture.numberFormat.NaN;
            }
            if (!format || format === "i") {
                return culture.name.length ? value.toLocaleString() : value.toString();
            }
            format = format || "D";

            var nf = culture.numberFormat,
                number = Math.abs(value),
                precision = -1,
                pattern;
            if (format.length > 1) precision = parseInt(format.slice(1), 10);

            var current = format.charAt(0).toUpperCase(),
                formatInfo,
                patterns;

            if (current == "N" || current == "D") {
                formatInfo = nf;
                patterns = g.patterns.numeric;
            }

            if (current == "D") {
                pattern = "n";
                number = truncate(number);
                if (precision !== -1) {
                    number = zeroPad("" + number, precision, true);
                }
                if (value < 0) number = "-" + number;
            }
            else if (current == "C") {
                formatInfo = nf.currency;
                patterns = g.patterns.currency;
            }
            else if (current == "P") {
                formatInfo = nf.percent;
                patterns = g.patterns.percent;
            }

            if (!formatInfo || !patterns) {
                throw "Bad number format specifier: " + current;
            }

            if (!pattern) {
                if (value < 0 && patterns.negative) {
                    pattern = patterns.negative[formatInfo.pattern[0]];
                }
                else if (value >= 0 && patterns.positive) {
                    pattern = patterns.positive[formatInfo.pattern[1]];
                }
            }

            pattern = pattern || "n";

            if (current != "D") {
                if (precision === -1) precision = formatInfo.decimals;
                number = expandNumber(number * (current === "P" ? 100 : 1), precision, formatInfo);
            }

            var patternParts = /n|\$|-|%/g,
                ret = "";
            for (; ;) {
                var index = patternParts.lastIndex,
                    ar = patternParts.exec(pattern);

                ret += pattern.slice(index, ar ? ar.index : pattern.length);

                if (!ar) {
                    break;
                }

                switch (ar[0]) {
                    case "n":
                        ret += number;
                        break;
                    case "$":
                        ret += nf.currency.symbol;
                        break;
                    case "-":
                        // don't make 0 negative
                        if (/[1-9]/.test(number)) {
                            ret += nf["-"];
                        }
                        break;
                    case "%":
                        ret += nf.percent.symbol;
                        break;
                }
            }

            return ret;
        };

        g.formatNumber = formatNumber;
    }());

    Smartstore.globalization = g;
})(jQuery);